package com.avcompris.util.junit;

import static com.avcompris.util.AvcIOUtils.countFiles;
import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static java.util.Locale.ENGLISH;
import static org.apache.commons.io.FileUtils.readFileToString;
import static org.apache.commons.lang3.StringUtils.join;
import static org.junit.Assert.assertEquals;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.lang3.ArrayUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.joda.time.DateTime;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.util.AbstractUtils;

/**
 * custom Hamcrest matchers. See the <a href="http://blog.xebia.fr/2008/04/02/simplifier-les-assertions-junit-et-ameliorer-vos-tests/"
 * >Xebia France's blog</a>.
 * 
 * @author David Andriana Copyright Avantage Compris SARL 2008-2009 ©
 */
public abstract class AvcMatchers extends AbstractUtils {

	/**
	 * create a new constraint on an integer.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>fileCount</tt>"
	 * @return a {@link Matcher} that will take an integer as a parameter
	 */
	public static <T> Matcher<T> equalTo(final int ref,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			/**
			 * assert the actual value equals to the ref value.
			 * 
			 *  @param object the ref value
			 *  @return <tt>true</tt> if the actual value equals to the ref value
			 */
			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof Integer)) {
					throw new NumberFormatException("Cannot parse integer: "
							+ object);
				}

				final Integer value = (Integer) object;

				return value.intValue() == ref;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" != ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an integer.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an integer as a parameter
	 */
	public static <T> Matcher<T> equalTo(final int ref) {

		return equalTo(ref, "int");
	}

	/**
	 * create a new constraint on an integer.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>fileCount</tt>"
	 * @return a {@link Matcher} that will take an integer as a parameter
	 */
	public static <T> Matcher<T> lesserThan(final int ref,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof Integer)) {
					throw new NumberFormatException("Cannot parse integer: "
							+ object);
				}

				final Integer value = (Integer) object;

				return value.intValue() < ref;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" < ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an integer.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an integer as a parameter
	 */
	public static <T> Matcher<T> lesserThan(final int ref) {

		return lesserThan(ref, "int");
	}

	/**
	 * create a new constraint on an integer.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>fileCount</tt>"
	 * @return a {@link Matcher} that will take an integer as a parameter
	 */
	public static <T> Matcher<T> greaterThan(final int ref,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof Integer)) {
					throw new NumberFormatException("Cannot parse integer: "
							+ object);
				}

				final Integer value = (Integer) object;

				return value.intValue() > ref;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" > ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an integer.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an integer as a parameter
	 */
	public static <T> Matcher<T> greaterThan(final int ref) {

		return greaterThan(ref, "int");
	}

	/**
	 * create a new constraint on an long.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "<tt>filesize</tt>
	 * @return a {@link Matcher} that will take a long as a parameter
	 */
	public static <T> Matcher<T> equalTo(final long ref,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof Long)) {
					throw new NumberFormatException("Cannot parse long: "
							+ object);
				}

				final Long value = (Long) object;

				return value.longValue() == ref;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" != ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an long.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take a long as a parameter
	 */
	public static <T> Matcher<T> equalTo(final long ref) {

		return equalTo(ref, "long");
	}

	/**
	 * create a new constraint on an long.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "<tt>filesize</tt>
	 *            "
	 * @return a {@link Matcher} that will take a long as a parameter
	 */
	public static <T> Matcher<T> greaterThan(final long ref,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof Long)) {
					throw new NumberFormatException("Cannot parse long: "
							+ object);
				}

				final Long value = (Long) object;

				return value.longValue() > ref;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" > ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an long.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take a long as a parameter
	 */
	public static <T> Matcher<T> greaterThan(final long ref) {

		return greaterThan(ref, "long");
	}

	/**
	 * create a new constraint on an long.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "<tt>filesize</tt>
	 *            "
	 * @return a {@link Matcher} that will take a long as a parameter
	 */
	public static <T> Matcher<T> lesserThan(final long ref,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof Long)) {
					throw new NumberFormatException("Cannot parse long: "
							+ object);
				}

				final Long value = (Long) object;

				return value.longValue() < ref;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" < ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an long.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take a long as a parameter
	 */
	public static <T> Matcher<T> lesserThan(final long ref) {

		return lesserThan(ref, "long");
	}

	/**
	 * create a new constraint on an date.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference date
	 * @return a {@link Matcher} that will take a date as a parameter
	 */
	public static <T> Matcher<T> lesserThan(final DateTime ref) {

		nonNullArgument(ref, "dateTimeRef");

		return lesserThan("refDate", ref, "date");
	}

	/**
	 * create a new constraint on an date.
	 * 
	 * @param <T> the actual value's type
	 * @param refDescription the description for the ref date value
	 * @param ref the reference date
	 * @param description the name of the value, for instance "<tt>fileDate</tt>
	 *            "
	 * @return a {@link Matcher} that will take a date as a parameter
	 */
	public static <T> Matcher<T> lesserThan(
			@Nullable final String refDescription, final DateTime ref,
			@Nullable final String description) {

		nonNullArgument(ref, "dateTimeRef");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof DateTime)) {
					throw new NumberFormatException("Cannot parse DateTime: "
							+ object);
				}

				final DateTime value = (DateTime) object;

				return value.getMillis() < ref.getMillis();
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" < ").appendText(
						refDescription).appendText(": ").appendValue(ref);
			}
		};
	}

	/**
	 * create a new constraint on an array's length.
	 * 
	 * @param <T> the actual value's type
	 * @param refArrayLength the reference length for the array
	 * @return a {@link Matcher} that will take an array as a parameter
	 */
	public static <T> Matcher<T> lengthGreaterOrEqualThan(
			final int refArrayLength) {

		return lengthGreaterOrEqualThan(refArrayLength, "array");
	}

	/**
	 * create a new constraint on an array's length.
	 * 
	 * @param <T> the actual value's type
	 * @param refArrayLength the reference length for the array
	 * @param description the name of the array, for instance "<tt>dir</tt>".
	 * @return a {@link Matcher} that will take an array as a parameter
	 */
	public static <T> Matcher<T> lengthGreaterOrEqualThan(
			final int refArrayLength, @Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object.getClass().isArray())) {
					throw new ClassCastException("Cannot parse array: "
							+ object);
				}

				final Object[] array = (Object[]) object;

				return array.length >= refArrayLength;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(".length >= ")
						.appendValue(refArrayLength);
			}
		};
	}

	/**
	 * create a new constraint on an object.
	 * 
	 * @param <T> the actual value's type
	 * @return a {@link Matcher} that will take an object as a parameter
	 */
	public static <T> Matcher<T> notNullValue() {

		return notNullValue("instance");
	}

	/**
	 * create a new constraint on an object.
	 * 
	 * @param <T> the actual value's type
	 * @param description the name of the value, for instance "<tt>file</tt>"
	 * @return a {@link Matcher} that will take an object as a parameter
	 */
	public static <T> Matcher<T> notNullValue(@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				return object != null;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should not be null");
			}
		};
	}

	/**
	 * create a new constraint on an object.
	 * 
	 * @param <T> the actual value's type
	 * @return a {@link Matcher} that will take an object as a parameter
	 */
	public static <T> Matcher<T> nullValue() {

		return nullValue("instance");
	}

	/**
	 * create a new constraint on an object.
	 * 
	 * @param <T> the actual value's type
	 * @param description the name of the value, for instance "<tt>file</tt>"
	 * @return a {@link Matcher} that will take an object as a parameter
	 */
	public static <T> Matcher<T> nullValue(@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				return object == null;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should be null");
			}
		};
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileExists() {

		return fileExists("file");
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @param description the name of the file, for instance "<tt>xmlFile</tt>"
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileExists(@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof File)) {
					throw new ClassCastException("Cannot parse File: " + object);
				}

				final File file = (File) object;

				return file.exists();
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" doesn't exist");
			}
		};
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileDoesntExist() {

		return fileDoesntExist("file");
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @param description the name of the file, for instance "<tt>xmlFile</tt>"
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileDoesntExist(
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof File)) {
					throw new ClassCastException("Cannot parse File: " + object);
				}

				final File file = (File) object;

				return !file.exists();
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should not exist");
			}
		};
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @param text the text to check the presence for in the given file
	 * @param description the name of the file, for instance "<tt>xmlFile</tt>"
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileContains(final String text,
			@Nullable final String description) {

		nonNullArgument(text, "text");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof File)) {
					throw new ClassCastException("Cannot parse File: " + object);
				}

				final File file = (File) object;

				final String content;

				try {
					content = readFileToString(file);
				} catch (final IOException e) {
					throw new RuntimeException(e);
				}

				return content.contains(text);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(
						" should contain text: [" + text + "]");
			}
		};
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @param text the text to check the presence for in the given file
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileContains(final String text) {

		return fileContains(text, "file");
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @param text the text to check the presence for in the given file
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileDoesntContain(final String text) {

		return fileDoesntContain(text, "file");
	}

	/**
	 * create a new constraint on a file.
	 * 
	 * @param <T> the actual value's type
	 * @param text the text to check the presence for in the given file
	 * @param description the name of the file, for instance "<tt>xmlFile</tt>"
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> fileDoesntContain(final String text,
			@Nullable final String description) {

		nonNullArgument(text, "text");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof File)) {
					throw new ClassCastException("Cannot parse File: " + object);
				}

				final File file = (File) object;

				final String content;

				try {
					content = readFileToString(file);
				} catch (final IOException e) {
					throw new RuntimeException(e);
				}

				return !content.contains(text);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(
						" should not contain text: [" + text + "]");
			}
		};
	}

	/**
	 * create a new constraint on a directory.
	 * 
	 * @param <T> the actual value's type
	 * @param refFileCount the reference file count
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> containsFileCount(final int refFileCount) {

		return containsFileCount(refFileCount, "file");
	}

	/**
	 * create a new constraint on a directory.
	 * 
	 * @param <T> the actual value's type
	 * @param refFileCount the reference file count
	 * @param description the name of the dir, for instance "<tt>outputDir</tt>"
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> containsFileCount(final int refFileCount,
			@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof File)) {
					throw new ClassCastException("Cannot parse File: " + object);
				}

				final File dir = (File) object;

				if (!dir.exists()) {
					throw new RuntimeException("Dir doesn't exist: "
							+ dir.getAbsolutePath());
				}

				final int fileCount = countFiles(dir);

				if (refFileCount != fileCount) {

					final Description d = new StringDescription();

					describeTo(d);

					assertEquals(d.toString(), refFileCount, fileCount);
				}

				return refFileCount == fileCount;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should contain ");

				switch (refFileCount) {
				case 0:
					s.appendText("no file");
					break;
				case 1:
					s.appendText("one file");
					break;
				default:
					s.appendValue(refFileCount);
					s.appendText(" files");
					break;
				}
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> startsWith(final String ref) {

		return startsWith(ref, "string");
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> startsWith(final String ref,
			@Nullable final String description) {

		nonNullArgument(ref, "ref");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof String)) {
					throw new ClassCastException("Cannot parse String: "
							+ object);
				}

				final String value = (String) object;

				return value.startsWith(ref);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should start with: \"")
						.appendValue(ref).appendText("\"");
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> endsWith(final String ref) {

		return endsWith(ref, "string");
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> endsWith(final String ref,
			@Nullable final String description) {

		nonNullArgument(ref, "ref");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof String)) {
					throw new ClassCastException("Cannot parse String: "
							+ object);
				}

				final String value = (String) object;

				return value.endsWith(ref);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should end with: \"")
						.appendValue(ref).appendText("\"");
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> contains(final String ref,
			@Nullable final String description) {

		nonNullArgument(ref, "ref");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object != null) {

					if (object instanceof String) {

						final String value = (String) object;

						return value.contains(ref);

					} else if (object instanceof String[]) {

						final String[] value = (String[]) object;

						return ArrayUtils.contains(value, ref);
					}
				}

				throw new ClassCastException(
						"Cannot parse String or String[]: " + object);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should contain: \"")
						.appendValue(ref).appendText("\"");
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> contains(final String ref) {

		return contains(ref, "text");
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @param description the name of the value, for instance "
	 *            <tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> doesntContain(final String ref,
			@Nullable final String description) {

		nonNullArgument(ref, "ref");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof String)) {
					throw new ClassCastException("Cannot parse String: "
							+ object);
				}

				final String value = (String) object;

				return !value.contains(ref);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should not contain: \"")
						.appendValue(ref).appendText("\"");
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param ref the reference value
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> doesntContain(final String ref) {

		return doesntContain(ref, "text");
	}

	public static <T> Matcher<T> isIn(final String[] ref) {

		return isIn(ref, "array");
	}

	public static <T> Matcher<T> isIn(final String[] ref,
			@Nullable final String description) {

		nonNullArgument(ref, "ref");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object != null && object instanceof String) {

					final String value = (String) object;

					return ArrayUtils.contains(ref, value);
				}

				throw new ClassCastException("Cannot parse String: " + object);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should be in: \"")
						.appendValue(join(ref, ", ")).appendText("\"");
			}
		};
	}

	public static <T> Matcher<T> isIn(final String methodName,
			final Object[] ref) {

		return isIn(methodName, ref, "text");
	}

	//TODO refactor, see what is common with AvcArrayUtils
	public static <T> Matcher<T> isIn(final String methodName,
			final Object[] ref, @Nullable final String description) {

		nonNullArgument(ref, "ref");
		nonNullArgument(methodName, "methodName");

		final Method method;

		try {

			method = ref.getClass().getComponentType().getMethod(methodName);

		} catch (final NoSuchMethodException e) {
			throw new RuntimeException("Cannot find method " + methodName
					+ "() in ref array: " + ref);
		}

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(@Nullable final Object value) {

				for (int i = 0; i < ref.length; ++i) {

					final Object item;

					try {

						item = method.invoke(ref[i]);

					} catch (final IllegalArgumentException e) {
						throw new RuntimeException(e);
					} catch (final IllegalAccessException e) {
						throw new RuntimeException(e);
					} catch (final InvocationTargetException e) {
						throw new RuntimeException(e);
					}

					if (item == null) {

						if (value == null) {
							return true;
						}

					} else {

						if (item.equals(value)) {
							return true;
						}
					}
				}

				return false;
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should be in: \"")
						.appendValue(join(ref, ", ")).appendText("\"");
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> isLowerCase() {

		return isLowerCase("string");
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param description the name of the value, for instance "
	 *            <tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> isLowerCase(@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof String)) {
					throw new ClassCastException("Cannot parse String: "
							+ object);
				}

				final String value = (String) object;

				return value.equals(value.toLowerCase(ENGLISH));
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should be lower case.");
			}
		};
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> isUpperCase() {

		return isUpperCase("string");
	}

	/**
	 * create a new constraint on an {@link String}.
	 * 
	 * @param <T> the actual value's type
	 * @param description the name of the value, for instance "
	 *            <tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an {@link String} as a parameter
	 */
	public static <T> Matcher<T> isUpperCase(@Nullable final String description) {

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				if (object == null || !(object instanceof String)) {
					throw new ClassCastException("Cannot parse String: "
							+ object);
				}

				final String value = (String) object;

				return value.equals(value.toUpperCase(ENGLISH));
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(" should be lower case.");
			}
		};
	}

	/**
	 * create a new constraint on an instance.
	 * 
	 * @param <T> the actual value's type
	 * @param c the class the instance should be an instance of
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> instanceOf(final Class<?> c) {

		return instanceOf(c, "class");
	}

	/**
	 * create a new constraint on an instance.
	 * 
	 * @param <T> the actual value's type
	 * @param c the class the instance should be an instance of
	 * @param description the name the bvalue, for instance "<tt>firstName</tt>"
	 * @return a {@link Matcher} that will take an file as a parameter
	 */
	public static <T> Matcher<T> instanceOf(final Class<?> c,
			@Nullable final String description) {

		nonNullArgument(c, "class");

		return new BaseMatcher<T>() {

			@Override
			public boolean matches(final Object object) {

				return object != null && c.isInstance(object);
			}

			@Override
			public void describeTo(final Description s) {

				s.appendText(description).appendText(
						" is not an instance of: " + c.getName());
			}
		};
	}
}
